import { Injectable, NotFoundException, BadRequestException, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like } from 'typeorm';
import { Script } from '../database/entities/script.entity';
import { ScriptExecution } from '../database/entities/script-execution.entity';
import { CreateScriptDto, UpdateScriptDto, QueryScriptDto } from './dto/scripts.dto';
import { ScriptsGateway } from './scripts.gateway';
import { spawn } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

@Injectable()
export class ScriptsService {
  constructor(
    @InjectRepository(Script)
    private scriptRepository: Repository<Script>,
    @InjectRepository(ScriptExecution)
    private executionRepository: Repository<ScriptExecution>,
    private scriptsGateway: ScriptsGateway,
  ) {}

  async create(createScriptDto: CreateScriptDto, userId?: number): Promise<Script> {
    const script = this.scriptRepository.create({
      ...createScriptDto,
      createdBy: userId,
      updatedBy: userId,
    });
    return this.scriptRepository.save(script);
  }

  async findAll(query: QueryScriptDto) {
    const { page = 1, pageSize = 10, name, type, status } = query;
    const skip = (page - 1) * pageSize;

    const where: any = {};
    if (name) where.name = Like(`%${name}%`);
    if (type) where.type = type;
    if (status) where.status = status;

    const [scripts, total] = await this.scriptRepository.findAndCount({
      where,
      skip,
      take: pageSize,
      order: { updatedAt: 'DESC' },
    });

    return {
      data: scripts,
      total,
      page,
      pageSize,
    };
  }

  async findOne(id: number): Promise<Script> {
    const script = await this.scriptRepository.findOne({ where: { id } });
    if (!script) {
      throw new NotFoundException(`Script with ID ${id} not found`);
    }
    return script;
  }

  async update(id: number, updateScriptDto: UpdateScriptDto, userId?: number): Promise<Script> {
    const script = await this.findOne(id);
    Object.assign(script, updateScriptDto, { updatedBy: userId });
    return this.scriptRepository.save(script);
  }

  async remove(id: number): Promise<void> {
    const script = await this.findOne(id);
    await this.scriptRepository.remove(script);
  }

  async execute(id: number, userId?: number, args?: string, parameters?: Record<string, string>): Promise<ScriptExecution> {
    const script = await this.findOne(id);
    
    // 安全检查
    if (script.status !== 'active') {
      throw new BadRequestException('Script is not active');
    }
    
    // 检查脚本内容中的危险命令
    this.validateScriptContent(script.content, script.type);
    
    // 检查参数
    if (args) {
      this.validateScriptArgs(args);
    }
    
    // 验证参数对象
    if (parameters) {
      this.validateParameterValues(parameters);
    }
    
    const execution = this.executionRepository.create({
      scriptId: id,
      status: 'running',
      startTime: new Date(),
      executedBy: userId,
    });
    
    const savedExecution = await this.executionRepository.save(execution);

    this.runScript(script, savedExecution, args, parameters);
    
    return savedExecution;
  }

  private validateScriptContent(content: string, type: 'node' | 'python' | 'bash') {
    const dangerousPatterns = [
      // 文件系统危险操作
      /\brm\s+-rf?\s+/gi,
      /\brmdir\s+/gi,
      /\bunlink\s*\(/gi,
      /\bfs\.unlink/gi,
      /\bfs\.rmdir/gi,
      /\bos\.remove/gi,
      /\bshutil\.rmtree/gi,
      
      // 网络请求到内网地址
      /\b(?:127\.0\.0\.1|localhost|192\.168\.|10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|0\.0\.0\.0)\b/gi,
      
      // 系统命令执行
      /\beval\s*\(/gi,
      /\bexec\s*\(/gi,
      /\bos\.system/gi,
      /\bsubprocess\.(?:call|run|Popen)/gi,
      /\bchild_process\.(?:exec|spawn|fork)/gi,
      
      // 文件读写危险路径
      /\/etc\/passwd/gi,
      /\/etc\/shadow/gi,
      /\/root\//gi,
      /\/var\/log/gi,
      /\/proc\//gi,
      /\/sys\//gi,
      
      // 环境变量访问敏感信息
      /\bprocess\.env\.(?:PASSWORD|SECRET|KEY|TOKEN)/gi,
      /\bos\.environ\.get\(['"](?:PASSWORD|SECRET|KEY|TOKEN)/gi,
    ];

    for (const pattern of dangerousPatterns) {
      if (pattern.test(content)) {
        throw new BadRequestException(`Script contains potentially dangerous code: ${pattern.source}`);
      }
    }
  }

  private validateScriptArgs(args: string) {
    const dangerousArgPatterns = [
      /[;&|`$()]/g, // Shell injection characters
      /\.\.\//g,    // Path traversal
      /^-/g,        // Options that start with dash
    ];

    for (const pattern of dangerousArgPatterns) {
      if (pattern.test(args)) {
        throw new BadRequestException('Script arguments contain potentially dangerous characters');
      }
    }

    // 限制参数长度
    if (args.length > 1000) {
      throw new BadRequestException('Script arguments too long');
    }
  }

  private validateParameterValues(parameters: Record<string, string>) {
    for (const [key, value] of Object.entries(parameters)) {
      // 验证参数名
      if (!/^[a-zA-Z0-9_-]+$/.test(key)) {
        throw new BadRequestException(`Invalid parameter name: ${key}`);
      }
      
      // 验证参数值
      if (typeof value !== 'string') {
        throw new BadRequestException(`Parameter value must be string: ${key}`);
      }
      
      // 检查危险字符
      const dangerousChars = /[;&|`$()]/g;
      if (dangerousChars.test(value)) {
        throw new BadRequestException(`Parameter value contains dangerous characters: ${key}`);
      }
      
      // 检查路径遍历
      if (value.includes('../')) {
        throw new BadRequestException(`Parameter value contains path traversal: ${key}`);
      }
      
      // 限制长度
      if (value.length > 1000) {
        throw new BadRequestException(`Parameter value too long: ${key}`);
      }
    }
  }

  private replaceScriptParameters(content: string, type: 'node' | 'python' | 'bash', parameters: Record<string, string>): string {
    let replacedContent = content;
    
    if (type === 'bash') {
      // 替换 --paramA value 形式的参数
      for (const [paramName, paramValue] of Object.entries(parameters)) {
        // 匹配 --paramName oldValue 并替换为 --paramName newValue
        const paramRegex = new RegExp(`(--${paramName})\\s+([^\\s-][^\\s]*(?:\\s+[^\\s-][^\\s]*)*)`, 'g');
        replacedContent = replacedContent.replace(paramRegex, `$1 ${paramValue}`);
        
        // 匹配 -paramName oldValue 形式（短参数）
        const shortParamRegex = new RegExp(`(-${paramName})\\s+([^\\s-][^\\s]*(?:\\s+[^\\s-][^\\s]*)*)`, 'g');
        replacedContent = replacedContent.replace(shortParamRegex, `$1 ${paramValue}`);
      }
      
      // 替换环境变量形式 ${VAR_NAME:-default_value}
      for (const [paramName, paramValue] of Object.entries(parameters)) {
        const envRegex = new RegExp(`\\$\\{${paramName}:-[^}]+\\}`, 'g');
        replacedContent = replacedContent.replace(envRegex, paramValue);
      }
    }
    
    return replacedContent;
  }

  private async runScript(script: Script, execution: ScriptExecution, args?: string, parameters?: Record<string, string>) {
    try {
      console.log(`[DEBUG] Running script ${script.id}, execution ${execution.id}`);
      
      let childProcess;
      let filePath: string | undefined;
      
      if (script.type === 'bash') {
        // 对于bash类型，直接执行命令，不创建临时文件
        let scriptContent = script.content;
        
        // 如果有参数替换，则替换脚本内容
        if (parameters && Object.keys(parameters).length > 0) {
          scriptContent = this.replaceScriptParameters(scriptContent, script.type, parameters);
          console.log(`[DEBUG] Script content after parameter replacement: ${scriptContent}`);
        }
        
        const fullCommand = args ? `${scriptContent} ${args}` : scriptContent;
        console.log(`[DEBUG] Executing bash command: ${fullCommand}`);
        
        childProcess = spawn('bash', ['-c', fullCommand], {
          stdio: 'pipe',
          shell: false,
          timeout: 300000, // 5分钟超时
          env: {
            // 限制环境变量，只保留必要的
            PATH: process.env.PATH,
            HOME: process.env.HOME,
            USER: process.env.USER,
            // 不暴露敏感环境变量
          },
          cwd: process.cwd(), // 使用当前工作目录
        });
      } else {
        // 对于node和python类型，创建临时文件执行
        const tempDir = os.tmpdir();
        const fileName = `script_${script.id}_${Date.now()}`;
        const fileExt = script.type === 'node' ? '.js' : '.py';
        filePath = path.join(tempDir, fileName + fileExt);

        fs.writeFileSync(filePath, script.content);
        console.log(`[DEBUG] Script written to ${filePath}`);
        console.log(`[DEBUG] Script content: ${script.content}`);

        const command = script.type === 'node' ? 'node' : 'python3';
        const scriptArgs = args ? args.split(' ').filter(arg => arg.trim()) : [];
        console.log(`[DEBUG] Executing: ${command} ${filePath} ${scriptArgs.join(' ')}`);
        
        // 设置执行超时和资源限制
        childProcess = spawn(command, [filePath, ...scriptArgs], {
          stdio: 'pipe',
          shell: false,
          timeout: 300000, // 5分钟超时
          env: {
            // 限制环境变量，只保留必要的
            PATH: process.env.PATH,
            NODE_PATH: process.env.NODE_PATH,
            PYTHONPATH: process.env.PYTHONPATH,
            // 不暴露敏感环境变量
          },
          cwd: tempDir, // 设置工作目录为临时目录
        });
      }

      let output = '';
      let error = '';

      childProcess.stdout.on('data', (data) => {
        const chunk = data.toString();
        output += chunk;
        console.log(`[DEBUG] Script ${execution.id} stdout: ${chunk}`);
        
        // 限制输出大小
        if (output.length > 1024 * 1024) { // 1MB 限制
          childProcess.kill('SIGTERM');
          return;
        }
        
        // 实时发送输出
        this.scriptsGateway.emitOutput(execution.id, chunk);
      });

      childProcess.stderr.on('data', (data) => {
        const chunk = data.toString();
        error += chunk;
        console.log(`[DEBUG] Script ${execution.id} stderr: ${chunk}`);
        
        // 限制错误输出大小
        if (error.length > 1024 * 1024) { // 1MB 限制
          childProcess.kill('SIGTERM');
          return;
        }
        
        // 实时发送错误信息
        this.scriptsGateway.emitError(execution.id, chunk);
      });

      childProcess.on('close', async (code) => {
        console.log(`[DEBUG] Script ${execution.id} closed with code: ${code}`);
        console.log(`[DEBUG] Script ${execution.id} output: ${output}`);
        console.log(`[DEBUG] Script ${execution.id} error: ${error}`);
        
        execution.status = code === 0 ? 'completed' : 'failed';
        execution.output = output;
        execution.error = error;
        execution.exitCode = code;
        execution.endTime = new Date();

        await this.executionRepository.save(execution);
        console.log(`[DEBUG] Script ${execution.id} execution saved to database`);
        
        // 发送状态更新
        this.scriptsGateway.emitStatusUpdate(execution.id, execution.status, code);
        console.log(`[DEBUG] Script ${execution.id} status update sent via websocket`);

        // 只有非bash类型脚本才需要清理临时文件
        if (filePath) {
          try {
            fs.unlinkSync(filePath);
          } catch (e) {
            console.warn('Failed to cleanup temp file:', filePath);
          }
        }
      });

      childProcess.on('error', async (err) => {
        execution.status = 'failed';
        execution.error = err.message;
        execution.endTime = new Date();
        await this.executionRepository.save(execution);
        
        // 发送状态更新和错误信息
        this.scriptsGateway.emitError(execution.id, err.message);
        this.scriptsGateway.emitStatusUpdate(execution.id, 'failed');

        // 只有非bash类型脚本才需要清理临时文件
        if (filePath) {
          try {
            fs.unlinkSync(filePath);
          } catch (e) {
            console.warn('Failed to cleanup temp file:', filePath);
          }
        }
      });

    } catch (error) {
      execution.status = 'failed';
      execution.error = error.message;
      execution.endTime = new Date();
      await this.executionRepository.save(execution);
      
      // 发送状态更新和错误信息
      this.scriptsGateway.emitError(execution.id, error.message);
      this.scriptsGateway.emitStatusUpdate(execution.id, 'failed');
    }
  }

  async getExecutions(scriptId: number, page = 1, pageSize = 10) {
    const skip = (page - 1) * pageSize;
    
    const [executions, total] = await this.executionRepository.findAndCount({
      where: { scriptId },
      skip,
      take: pageSize,
      order: { createdAt: 'DESC' },
    });

    return {
      data: executions,
      total,
      page,
      pageSize,
    };
  }

  async getExecution(id: number): Promise<ScriptExecution> {
    if (!id || isNaN(id) || id <= 0) {
      throw new BadRequestException(`Invalid execution ID: ${id}`);
    }
    
    const execution = await this.executionRepository.findOne({ 
      where: { id },
      relations: ['script']
    });
    
    if (!execution) {
      throw new NotFoundException(`Execution with ID ${id} not found`);
    }
    
    return execution;
  }

  async extractScriptParameters(id: number) {
    const script = await this.findOne(id);
    return this.parseScriptParameters(script.content, script.type);
  }

  private parseScriptParameters(content: string, type: 'node' | 'python' | 'bash') {
    const parameters: Array<{ name: string; defaultValue: string; description?: string }> = [];
    
    if (type === 'bash') {
      // 匹配 --paramA value 或 -p value 形式的参数
      const paramRegex = /--?([a-zA-Z0-9_-]+)\s+([^\s-][^\s]*(?:\s+[^\s-][^\s]*)*)/g;
      let match;
      
      while ((match = paramRegex.exec(content)) !== null) {
        const paramName = match[1];
        const defaultValue = match[2];
        
        // 检查是否已经存在相同参数
        if (!parameters.find(p => p.name === paramName)) {
          parameters.push({
            name: paramName,
            defaultValue: defaultValue.trim(),
            description: `参数 ${paramName}`
          });
        }
      }
      
      // 匹配环境变量形式 ${VAR_NAME:-default_value}
      const envVarRegex = /\$\{([a-zA-Z0-9_]+):-([^}]+)\}/g;
      while ((match = envVarRegex.exec(content)) !== null) {
        const paramName = match[1];
        const defaultValue = match[2];
        
        if (!parameters.find(p => p.name === paramName)) {
          parameters.push({
            name: paramName,
            defaultValue: defaultValue,
            description: `环境变量 ${paramName}`
          });
        }
      }
    } else if (type === 'python') {
      // 匹配 argparse 参数定义
      const argparseRegex = /add_argument\s*\(\s*['"]--?([^'"]+)['"](?:[^,)]*,)?\s*(?:default\s*=\s*['"]([^'"]*)['"]\s*[,)]|.*?default\s*=\s*['"]([^'"]*)['"]\s*[,)])/g;
      let match;
      
      while ((match = argparseRegex.exec(content)) !== null) {
        const paramName = match[1];
        const defaultValue = match[2] || match[3] || '';
        
        if (!parameters.find(p => p.name === paramName)) {
          parameters.push({
            name: paramName,
            defaultValue: defaultValue,
            description: `参数 ${paramName}`
          });
        }
      }
      
      // 匹配 sys.argv 使用
      const sysArgvRegex = /sys\.argv\[(\d+)\]/g;
      const argIndices = new Set<number>();
      while ((match = sysArgvRegex.exec(content)) !== null) {
        argIndices.add(parseInt(match[1]));
      }
      
      Array.from(argIndices).sort().forEach((index, i) => {
        if (index > 0) { // sys.argv[0] 是脚本名
          const paramName = `arg${index}`;
          if (!parameters.find(p => p.name === paramName)) {
            parameters.push({
              name: paramName,
              defaultValue: '',
              description: `位置参数 ${index}`
            });
          }
        }
      });
    } else if (type === 'node') {
      // 匹配 process.argv 使用
      const processArgvRegex = /process\.argv\[(\d+)\]/g;
      let match;
      const argIndices = new Set<number>();
      
      while ((match = processArgvRegex.exec(content)) !== null) {
        argIndices.add(parseInt(match[1]));
      }
      
      Array.from(argIndices).sort().forEach((index, i) => {
        if (index >= 2) { // process.argv[0] 是node路径，process.argv[1] 是脚本路径
          const paramName = `arg${index - 1}`;
          if (!parameters.find(p => p.name === paramName)) {
            parameters.push({
              name: paramName,
              defaultValue: '',
              description: `位置参数 ${index - 1}`
            });
          }
        }
      });
      
      // 匹配 yargs 或其他参数解析库
      const yargsRegex = /\.option\s*\(\s*['"]([^'"]+)['"](?:[^,}]*,)?\s*\{[^}]*default\s*:\s*['"]([^'"]*)['"]/g;
      while ((match = yargsRegex.exec(content)) !== null) {
        const paramName = match[1];
        const defaultValue = match[2] || '';
        
        if (!parameters.find(p => p.name === paramName)) {
          parameters.push({
            name: paramName,
            defaultValue: defaultValue,
            description: `参数 ${paramName}`
          });
        }
      }
    }
    
    return parameters;
  }
}